#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <assert.h>
#include <algorithm>
#include <fstream>
#include <jsoncpp/json/json.h>

#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"

#include "oj_model.hpp"
#include "oj_view.hpp"

namespace ns_control
{

    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;

    // 提供服务的主机
    struct Machine
    {
    public:
        std::string _ip;    // ip
        int _port;          // port
        uint64_t _load;     // 负载（共享资源）
        std::mutex *_mutex; // mutex 禁止拷贝，如果放入容器中，对象是会发生拷贝的，所以这里使用指针

    public:
        Machine()
            : _ip(""), _port(0), _load(0), _mutex(new mutex)
        {
        }
        Machine(const std::string &ip, int port)
            : _ip(ip), _port(port), _load(0), _mutex(new mutex)
        {
        }
        ~Machine()
        {
            // delete _mutex;
        }
        // 主机负载 ++
        void IncreaseLoad()
        {
            _mutex->lock();
            _load++;

            _mutex->unlock();
        }
        // 主机负载 --
        void DecreaseLoad()
        {
            _mutex->lock();
            _load--;
            _mutex->unlock();
        }
        uint64_t Load() const
        {
            uint64_t loadnum = 0;
            _mutex->lock();
            loadnum = _load;
            _mutex->unlock();
            return loadnum;
        }
        void ResetLoad()
        {
            _mutex->lock();
            _load = 0;
            _mutex->unlock();
        }
    };

    // 可以用来进行编译的所有的主机信息都放在该文件下
    const std::string service_machine = "./conf/service_machine.conf";

    // 负载均衡
    class LoadBalance
    {
    private:
        std::vector<Machine> _machines; // 提供编译服务的所有主机，下标表示主机 id
        std::vector<int> _online;       // 所有在线主机的 id    (共享资源)
        std::vector<int> _offline;      // 所有离线的主机的 id  （共享资源）
        std::mutex _mutex;

    public:
        LoadBalance()
        {
            assert(LoadConf(service_machine));
            LOG(INFO) << "加载 server_macnine 成功" << std::endl;
        }
        ~LoadBalance()
        {
        }

    public:
        // 将配置文件中的所有机器加载到内存中，放入 _machines 中管理起来
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(ERROR) << "加载 machine_conf 配置文件失败" << std::endl;
                return false;
            }

            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> target;
                StringUtil::SplitString(line, &target, ":");
                if (target.size() != 2)
                {
                    LOG(WARNING) << "获取单个 machine 失败" << std::endl;
                    continue;
                }
                Machine mach(target[0], std::stoi(target[1]));

                _online.push_back(_machines.size());
                _machines.push_back(std::move(mach));
            }
            ShowMachines();
            in.close();
            return true;
        }
        // 智能选择
        bool SmartChoice(int *id, Machine **mach)
        {
            // 会有不同的用户同时进入该函数,所以进行一下加锁
            _mutex.lock();

            int online_num = _online.size();
            if (online_num == 0)
            {
                _mutex.unlock();

                LOG(FATA) << "所有的后端编译主机都已经离线，请运维尽快查看" << std::endl;
                return false;
            }

            // 从 _online(在线主机) 中找负载最小的机器
            uint64_t min_load = _machines[_online[0]].Load();
            *id = _online[0];
            *mach = &_machines[_online[0]];
            for (int i = 1; i < online_num; i++)
            {
                uint64_t cur_load = _machines[_online[i]].Load();
                if (cur_load < min_load)
                {
                    min_load = cur_load;
                    *id = _online[i];
                    *mach = &_machines[_online[i]];
                }
            }
            _mutex.unlock();
            return true;
        }
        // 将一个主机离线
        bool OfflineMachine(int id)
        {
            _mutex.lock();
            // auto iter= _online.find(id); //注：这种写法不可以，因为 vector中没有成员 find

            for (auto iter = _online.begin(); iter != _online.end(); iter++)
            {
                if (*iter == id)
                {
                    _online.erase(iter);

                    _machines[id].ResetLoad(); // 离线了，负载清零,否则下次再上线, 仍然是下线前的 load 值
                                               // 这里不能直接 _machines[id]._load = 0 ，因为这片区域虽然是上锁了，只有一个进程可以拿到这个这个锁进入
                                               // 然后一个进程访问 _machines 中的某个机器，将它的 load 变为0，但同时也有可能在其他地方有其他线程正在访问
                                               // 这个的 load 值，对 load 的值的访问是加锁的，但加的锁和当前的锁不是一个，所以那边拥有锁之后是可以访问的，
                                               // 而这边拥有的是另一个锁，正在修改，就会有同时访问共享资源的问题了

                    _offline.push_back(id); // push_back(*iter) 是错误的，因为先删除了 iter，再插入时使用 iter是失效的

                    _mutex.unlock();
                    return true;
                }
            }
            _mutex.unlock();
            return false;
        }

        // 将所有主机上线
        bool OnlineMachine()
        {
            _mutex.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.clear();

            _mutex.unlock();
            LOG(INFO) << "所有的主机又上线了" << std::endl;
            return true;
        }

        // for test
        void ShowMachines()
        {
            _mutex.lock();

            std::cout << "在线主机列表：";
            for (auto &id : _online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            std::cout << "离线主机列表：";
            for (auto &id : _offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            _mutex.unlock();
        }
    };

    // 负责业务逻辑
    class Control
    {
    private:
        Model _model;

        View _view;
        LoadBalance _load_balance;

    public:
        Control()
        {
        }

        void RecoverMachine()
        {
            _load_balance.OnlineMachine();
        }
        // 所有题目数据，构建一个网页
        //  html：输出型参数
        bool AllQuestions(std::string *html)
        {
            std::vector<Question> all;
            if (!_model.GetAllQuestions(&all))
            {
                *html = "形成题目列表失败";
                return false;
            }

            // 题目根据 number 进行排序
            std::sort(all.begin(), all.end(), [](const Question &q1, const Question &q2)
                      { return std::stoi(q1.number) < std::stoi(q2.number); });
            _view.AllExpandHtml(all, html);

            return true;
        }
        // 根据题目编号获取当前题目详细信息的一个网页
        bool OneQuestion(const std::string &number, std::string *html)
        {
            Question q;
            if (!_model.GetOneQuestion(number, &q))
            {
                *html = "获取题目失败";
                return false;
            }
            _view.OneExpandHtml(q, html);

            return true;
        }
        void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            Question q;
            _model.GetOneQuestion(number, &q);

            // 字符串 -> 结构化数据

            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);

            // 构建用于 compile 的 json 串

            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            std::string code = in_value["code"].asString();
            compile_value["code"] = code + "\n" + q.tail;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;

            Json::FastWriter writer;
            std::string compile_json = writer.write(compile_value);

            // 选择负载最低的主机
            while (true)
            {
                int id = 0;
                Machine *mach = nullptr;

                if (!_load_balance.SmartChoice(&id, &mach))
                {
                    break;
                }
                mach->IncreaseLoad();
                LOG(INFO) << "选择主机成功,主机 id: " << id << ",主机详情：" << mach->_ip << ":" << mach->_port << ",主机当前负载为：" << mach->Load() << std::endl;

                httplib::Client cli(mach->_ip, mach->_port);
                std::cout << "client:" << mach->_ip << " " << mach->_port << std::endl;
                if (auto res = cli.Post("/compile_and_run", compile_json, "application/json;charset=utf-8"))
                {
                    if (res->status == 200) // hppt 请求的状态码，只有是 200 的时候才是请求成功的
                    {
                        *out_json = res->body;
                        mach->DecreaseLoad();
                        LOG(INFO) << "请求编译和运行服务成功" << std::endl;
                        break;
                    }
                    mach->DecreaseLoad();
                    // 走到这里说明进行 http 请求失败了，它会继续 while 重新选择机器，再请求，直到 http 请求成功
                    LOG(WARNING) << "本次请求状态码错误，已重新请求" << std::endl;
                }
                else
                {
                    LOG(ERROR) << "当前主机请求失败，主机可能已经离线,主机 id: " << id << ",主机详情：" << mach->_ip << ":" << mach->_port << std::endl;
                    mach->DecreaseLoad(); // 这里有没有都可以，因为下面的离线会将该 mach 的load 清零
                    _load_balance.OfflineMachine(id);

                    _load_balance.ShowMachines();
                }
            }
        }
    };
}